package com.king.pages.novel.form;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import com.king.App;
import com.king.event.FormDialogEvent;
import com.king.event.NotificationEvent;
import com.king.mind.ChapterNode;
import com.king.mind.Mind;
import com.king.mind.Node;
import com.king.mind.VolumeNode;
import com.king.pages.novel.entity.Chapter;
import com.king.pages.novel.service.NovelService;
import com.king.util.Async;

import atlantafx.base.controls.CustomTextField;
import atlantafx.base.theme.Styles;
import ch.qos.logback.core.util.StringUtil;
import javafx.animation.PauseTransition;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
import lombok.extern.slf4j.Slf4j;
import net.rgielen.fxweaver.core.FxmlView;

/**
 * 书-表单-controller
 * 
 * @author king
 * @date 2025年7月7日-16:30:03
 */
@Slf4j
@FxmlView
@Component
public class NovelForm {

	@FXML
	private SplitPane box;

	@FXML
	private AnchorPane right;

	@FXML
	private HBox volumeBox;

	@FXML
	private TextField title;

	@FXML
	private TextArea label;

	@FXML
	private TextArea description;

	@FXML
	private TextArea content;

	@FXML
	private HBox findBar;

	@FXML
	private CustomTextField findInput;

	@FXML
	private Label findView;

	@Autowired
	private Async async;

	@Autowired
	private NovelService novelService;

	private Mind mind;

	/**
	 * Mind的一些基本信息显示，可以用
	 */
	private Label mindInfo;

	/**
	 * 定义一个动画，用来处理mindInfo的显示，因为mindInfo不能一直显示着，太碍眼，让其指定时间显示即可。
	 */
	private PauseTransition mindInfoAnimation;

	/**
	 * 记录所有匹配的字符的起始值和结束值
	 */
	private List<int[]> findRange;

	/**
	 * 记录当前标注寻找到的字符的findRnage的下标
	 */
	private int findIndex = -1;

	@Autowired
	private ApplicationEventPublisher publisher;

	/**
	 * 当前书ID
	 */
	private String currentNovelId;

	/**
	 * 当前章节ID
	 */
	private String currentChapterId;


	/**
	 * 是否有修改过，没有修改过，那么保存触发时，无需保存
	 */
	private boolean hasChange;

	/**
	 * 当前是否在加载数据，如果是在加载数据，那么hasChange的监听器触发应不要生效
	 */
	private boolean loading;

	@FXML
	private void initialize() {
		mind = new Mind((chapterId) -> {
			loadChapterInfo(chapterId);
		}, (msg) -> {
			mindInfo.setText(msg);
			mindInfo.setVisible(true);
			// 当Mind调用此方法后，那么动画便重新开始
			mindInfoAnimation.playFromStart();
		});
		right.getChildren().add(mind);
		mind.widthProperty().bind(right.widthProperty());
		mind.heightProperty().bind(right.heightProperty());

		// 不支持在FXML设计UI，只能在这里给其左侧添加一个放大镜的图标
		findInput.setLeft(FontIcon.of(Feather.SEARCH));
		// 因为没有change事件，那么只能在此添加一个监听，如果输入框的内容变化，那么我们将当前查询的索引设置为-1
		findInput.textProperty().addListener((_) -> {
			findIndex = -1;
		});

		findInput.setOnKeyReleased(this::onEnterFind);

		/**
		 * 查询栏一开始不显示，按下CTRL+F后再显示
		 */
		findBar.setManaged(false);
		findBar.setVisible(false);

		// 给整个表单注册键盘事件
		box.setOnKeyReleased(this::regKeyDown);

		mindInfo = new Label();
		mindInfo.setVisible(false);
		mindInfo.setStyle("-fx-fill:rgba(255, 255, 255, 0.66);-fx-padding:10 10;-fx-background-color:#FF4D1860");
		AnchorPane.setRightAnchor(mindInfo, 0D);
		right.getChildren().add(mindInfo);

		/*
		 * 5s的延迟执行
		 */
		mindInfoAnimation = new PauseTransition(Duration.seconds(5));
		mindInfoAnimation.setOnFinished((_) -> mindInfo.setVisible(false));
		formChange();
	}

	/**
	 * 表单change事件处理
	 * 
	 * @author king
	 * @date 2025年7月16日-17:20:37
	 */
	private void formChange() {
		// 初始hasChange值为false
		hasChange = false;
		changeListener(title.textProperty());
		changeListener(label.textProperty());
		changeListener(content.textProperty());
		changeListener(description.textProperty());
	}

	/**
	 * 如果表单有值发生变化，那么将其设置为true
	 * 
	 * @author king
	 * @date 2025年7月16日-16:36:24
	 */
	private void changeListener(StringProperty pro) {
		pro.addListener((_) -> {
			if (!loading) {
				hasChange = true;
			}
		});
	}

	/**
	 * 当查询框收到回车键时，开始查询
	 * 
	 * @param e
	 * @author king
	 * @date 2025年7月15日-11:35:19
	 */
	private void onEnterFind(KeyEvent e) {
		if (App.ON_ENTER.match(e)) {
			find();
		}
	}

	/**
	 * 
	 * @param e
	 * @author king
	 * @date 2025年7月11日-15:24:33
	 */
	private void regKeyDown(KeyEvent e) {
		if (App.ON_FIND.match(e)) {
			// 如果组合键是CTRL+F 这里要显示查询栏，处理一下，如果查询栏显示，那么就隐藏，否则显示
			if (!"".equals(content.getSelectedText())) {
				findBar.setManaged(true);
				findBar.setVisible(true);
				findInput.setText(content.getSelectedText());
				find();
			} else {
				findBar.setManaged(!findBar.isManaged());
				findBar.setVisible(!findBar.isVisible());
			}
			if (findBar.isVisible()) {
				// 如果显示了查询栏，那么将焦点放到输入框内
				findInput.requestFocus();
			}
		} else if (App.ON_SAVE.match(e)) {
			// CTRL+S保存
			onSave();
		}
	}

	/**
	 * 添加章节，添加章节成功后，将添加一个节点到屏幕，并移动到该节点上
	 * 
	 * @author king
	 * @date 2025年7月17日-13:41:51
	 */
	@FXML
	private void onAdd() {
		if (currentNovelId == null) {
			publisher.publishEvent(NotificationEvent.build("书ID数据异常，关闭弹框后重新进入！").styles(Styles.DANGER));
			return;
		}
		async.done(() -> {
			return novelService.addNovel(this.currentNovelId);
		}, (d) -> {
			this.mind.addNodes(d.toNode());
		});

	}

	/**
	 * 保存
	 */
	@FXML
	private void onSave() {
		if (currentChapterId == null) {
			publisher.publishEvent(NotificationEvent.build("请先选择一个章节！").autoClose(true).styles(Styles.DANGER));
			return;
		}

		if (!hasChange) {
			publisher.publishEvent(NotificationEvent.build("未做更改，无需保存！").autoClose(true).styles(Styles.DANGER));
			return;
		}
		// 收集表单数据
		Chapter ch = new Chapter();

		ch.setNum(findIndex);
		ch.setId(currentChapterId);
		ch.setTitle(title.getText());
		ch.setWord(content.getText());
		ch.setDescription(description.getText());
		if (!StringUtil.isNullOrEmpty(label.getText())) {
			ch.setLabels(label.getText().split("\n+"));
		}
		async.done(() -> {
			return novelService.save(ch);
		}, (d) -> {
			hasChange = false;
			if (d) {
				mind.reDraw(ch.toNode());
				// 保存时，提示保存的情况
				publisher.publishEvent(NotificationEvent.build("保存成功！").autoClose(true));
			} else {
				publisher.publishEvent(NotificationEvent.build("保存失败！").autoClose(true));
			}
		});
	}

	/**
	 * 删除最后一章，最后一章须无内容（标签，内容，描述）
	 * 
	 * @author king
	 * @date 2025年7月22日-09:44:42
	 */
	@FXML
	private void onDel() {
		async.done(() -> {
			return novelService.delLastChapter(this.currentNovelId);
		}, (d) -> {
			publisher.publishEvent(NotificationEvent.build("成功删除最后一章节！").autoClose(true));
			mind.delNode(d);
		});
	}

	/**
	 * 匹配，直接使用正则
	 * 
	 * @author king
	 * @date 2025年7月11日-15:28:58
	 */
	private void find() {
		if (findIndex != -1) {
			// 敲击回车键会触发此方法，那么如果不是第一次，那么将调用查询到下一个
			findNext();
			return;
		}
		if ("".equals(findInput.getText())) {
			// 查询框没有查询条件时，无须执行后续逻辑
			return;
		}

		findRange = new ArrayList<>();
		Pattern p = Pattern.compile(findInput.getText());
		Matcher m = p.matcher(content.getText());
		//这里需要记录寻找到的索引数组，起始值和结束值
		int[] range;
		while(m.find()) {
			range = new int[2];
			range[0] = m.start();
			range[1] = m.end();
			findRange.add(range);
		}
		if (findRange.size() > 0) {
			findIndex(0);
		} else {
			findView.setText("未匹配到字符");
		}
	}

	/**
	 * 查询-上一个
	 * 
	 * @author king
	 * @date 2025年7月11日-15:37:50
	 */
	@FXML
	private void findPre() {
		if (findRange == null || findRange.size() == 0) {
			return;
		}
		findIndex--;
		// 如果上一个低于第一个，那么将其回到最后一个
		if (findIndex < 0) {
			findIndex = findRange.size() - 1;
		}
		findIndex(findIndex);
	}

	/**
	 * 查询-下一个
	 * 
	 * @author king
	 * @date 2025年7月11日-15:38:06
	 */
	@FXML
	private void findNext() {
		if (findRange == null || findRange.size() == 0) {
			return;
		}
		findIndex++;
		// 如果下一个已经到达末尾，那么将回到第一个
		if (findIndex >= findRange.size()) {
			findIndex = 0;
		}
		findIndex(findIndex);
	}

	/**
	 * 记录当前下标，并标注
	 * 
	 * @author king
	 * @date 2025年7月11日-15:34:37
	 */
	private void findIndex(int index) {
		findIndex = index;
		findView.setText(String.format("%s / %s", index + 1, findRange.size()));
		content.selectRange(findRange.get(index)[0], findRange.get(index)[1]);
	}

	/**
	 * 加载书本信息到Mind
	 * 
	 * @param novelId
	 * @author king
	 * @date 2025年7月11日-12:49:26
	 */
	public void loadData(String novelId) {
		if (hasChange) {
			// 如果加载数据，那么先校验是否有变化，有，那么先存档后再加载
			onSave();
		}
		this.currentNovelId = novelId;
		async.done(() -> {
			return novelService.loadBookData(novelId);
		}, (d) -> {
			mind.clear();
			resetForm();
			List<Node> nodes = new ArrayList<>();
			List<String> volumeTitles = new ArrayList<>();
			for (VolumeNode n : d) {
				nodes.add(n);
				volumeTitles.add(n.getTitle());
				for (ChapterNode nn : n.getChildren()) {
					nodes.add(nn);
					if (this.currentChapterId != null && this.currentChapterId.equals(nn.getId())) {
						nn.setSelected(true);
					}
				}
			}
			mind.addNodes(nodes.toArray(new Node[0]));
			Label l;
			int i = 0;
			// 显示卷标签，最后一个卷即当前使用的卷，使用danger显示
			for (String v : volumeTitles) {
				l = new Label(v);
				l.setStyle("-fx-padding:12;");
				if (i == volumeTitles.size() - 1) {
					l.getStyleClass().addAll("k-active", Styles.DANGER);
				} else {
					l.getStyleClass().addAll("k-active", Styles.ACCENT);
				}
				volumeBox.getChildren().add(l);
				i++;
			}
		});
	}

	/**
	 * 重置表单数据
	 * 
	 * @author king
	 * @date 2025年7月11日-12:52:41
	 */
	private void resetForm() {
		// 因为使用了文本的监听，这里修改文本，视作加载情况，不用去修改hasChange变量
		this.loading = true;
		this.content.clear();
		this.title.clear();
		this.label.clear();
		this.description.clear();
		this.findIndex = -1;
		this.findRange = null;
		this.findInput.clear();
		this.loading = false;
	}

	@FXML
	private void close() {
		publisher.publishEvent(FormDialogEvent.close());
	}

	/**
	 * 获取单个章节的数据信息
	 * 
	 * @param chapterId
	 * @author king
	 * @date 2025年7月11日-12:49:44
	 */
	public void loadChapterInfo(String chapterId) {
		async.done(() -> {
			loading = true;
			return novelService.selChapterInfo(chapterId);
		}, (d) -> {
			if (d != null) {
				this.findView.setText("");
				this.currentChapterId = d.getId();

				this.findIndex = -1;

				if (d.getLabels() != null) {
					int i = 0;
					StringBuilder sb = new StringBuilder();
					for (String l : d.getLabels()) {
						sb.append(l);
						if (i != d.getLabels().length) {
							sb.append("\n");
						}
						i++;
					}
					this.label.setText(sb.toString());
				} else {
					this.label.clear();
				}

				this.title.setText(d.getTitle());
				this.content.setText(d.getWord());
				this.description.setText(d.getDescription());
			}
			loading = false;
		});
	}
}
